#include "rtweekend.h"

#include "Hit/hittable.h"
#include "Hit/hittable_list.h"
#include "Hit/sphere.h"
#include "camera.h"
#include "material.h"
#include "bvh.h"
#include "texture.h"
#include "quad.h"
#include "Hit/constant_medium.h"

void bouncing_spheres();
void checkered_spheres();
void earth();
void perlin_spheres();
void quads();
void simple_light();
void cornell_box();
void cornell_smoke();
void final_scene(int image_width, int samples_per_pixel, int max_depth);

int main() 
{
	switch (7) 
	{
	case 1: bouncing_spheres();  break;
	case 2: checkered_spheres(); break;
	case 3: earth();             break;
	case 4: perlin_spheres();	 break;
	case 5: quads();			 break;
	case 6: simple_light();		 break;
	case 7: cornell_box();		 break;
	case 8: cornell_smoke();	 break;
	case 9:  final_scene(800, 10000, 40); break;
	default: final_scene(400, 250, 4); break;
	}
}

void bouncing_spheres()
{
	hittable_list world;

	auto checker = make_shared<checker_texture>(0.32, color(.2, .3, .1), color(.9, .9, .9));
	world.add(make_shared<sphere>(point3(0, -1000, 0), 1000, make_shared<lambertian>(checker)));

	for (int a = -11; a < 11; a++) {
		for (int b = -11; b < 11; b++) {
			auto choose_mat = random_double();
			point3 center(a + 0.9 * random_double(), 0.2, b + 0.9 * random_double());

			if ((center - point3(4, 0.2, 0)).length() > 0.9) {
				shared_ptr<material> sphere_material;

				if (choose_mat < 0.8) {
					// diffuse
					auto albedo = color::random() * color::random();
					sphere_material = make_shared<lambertian>(albedo);
					auto center2 = center + vec3(0, random_double(0, .5), 0);
					world.add(make_shared<sphere>(center, center2, 0.2, sphere_material));
				}
				else if (choose_mat < 0.95) {
					// metal
					auto albedo = color::random(0.5, 1);
					auto fuzz = random_double(0, 0.5);
					sphere_material = make_shared<metal>(albedo, fuzz);
					world.add(make_shared<sphere>(center, 0.2, sphere_material));
				}
				else {
					// glass
					sphere_material = make_shared<dielectric>(1.5);
					world.add(make_shared<sphere>(center, 0.2, sphere_material));
				}
			}
		}
	}

	auto material1 = make_shared<dielectric>(1.5);
	world.add(make_shared<sphere>(point3(0, 1, 0), 1.0, material1));

	auto material2 = make_shared<lambertian>(color(0.4, 0.2, 0.1));
	world.add(make_shared<sphere>(point3(-4, 1, 0), 1.0, material2));

	auto material3 = make_shared<metal>(color(0.7, 0.6, 0.5), 0.0);
	world.add(make_shared<sphere>(point3(4, 1, 0), 1.0, material3));

	world = hittable_list(make_shared<bvh_node>(world));
	camera cam(16.0 / 9.0, 400, 20, point3(13, 2, 3), point3(0, 0, 0));

	cam.set_defocus_angle(0.6);
	cam.set_focus_dist(10);
	cam.set_samples_per_pixel(5);
	cam.set_max_depth(50);
	cam.set_background(color(0.70, 0.80, 1.00));

	cam.render(world);
}

void checkered_spheres()
{
	hittable_list world;

	auto checker = make_shared<checker_texture>(0.32, color(.2, .3, .1), color(.9, .9, .9));

	world.add(make_shared<sphere>(point3(0, -10, 0), 10, make_shared<lambertian>(checker)));
	world.add(make_shared<sphere>(point3(0, 10, 0), 10, make_shared<lambertian>(checker)));

	camera cam(16.0 / 9.0, 400, 20, point3(13, 2, 3), point3(0, 0, 0));

	cam.set_samples_per_pixel(100);
	cam.set_max_depth(50);
	cam.set_defocus_angle(0);
	cam.set_background(color(0.70, 0.80, 1.00));

	cam.render(world);
}

void earth()
{
	hittable_list world;

	auto earth_texture = make_shared<image_texture>("earthmap.jpg");
	auto earth_surface = make_shared<lambertian>(earth_texture);
	auto globe_1 = make_shared<sphere>(point3(2, 0, 0), 2, earth_surface);
	auto globe_2 = make_shared<sphere>(point3(-2, 0, 0), 2, earth_surface);

	world.add(globe_1);
	world.add(globe_2);

	camera cam(16.0 / 9.0, 400, 20, point3(0, 0, 12), point3(0, 0, 0));

	cam.set_samples_per_pixel(100);
	cam.set_max_depth(50);
	cam.set_defocus_angle(0);
	cam.set_background(color(0.70, 0.80, 1.00));

	cam.render(hittable_list(world));
}

void perlin_spheres()
{
	hittable_list world;

	auto pertext = make_shared<noise_texture>(4);
	world.add(make_shared<sphere>(point3(0, -1000, 0), 1000, make_shared<lambertian>(pertext)));
	world.add(make_shared<sphere>(point3(0, 2, 0), 2, make_shared<lambertian>(pertext)));

	camera cam(16.0 / 9.0, 400, 20, point3(13, 2, 3), point3(0, 0, 0));

	cam.set_samples_per_pixel(100);
	cam.set_max_depth(50);
	cam.set_defocus_angle(0);
	cam.set_background(color(0.70, 0.80, 1.00));

	cam.render(world);
}

void quads()
{
	hittable_list world;

	// Materials
	auto left_red = make_shared<lambertian>(color(1.0, 0.2, 0.2));
	auto back_green = make_shared<lambertian>(color(0.2, 1.0, 0.2));
	auto right_blue = make_shared<lambertian>(color(0.2, 0.2, 1.0));
	auto upper_orange = make_shared<lambertian>(color(1.0, 0.5, 0.0));
	auto lower_teal = make_shared<lambertian>(color(0.2, 0.8, 0.8));

	// Quads
	world.add(make_shared<quad>(point3(-3, -2, 5), vec3(0, 0, -4), vec3(0, 4, 0), left_red));
	world.add(make_shared<quad>(point3(-2, -2, 0), vec3(4, 0, 0), vec3(0, 4, 0), back_green));
	world.add(make_shared<quad>(point3(3, -2, 1), vec3(0, 0, 4), vec3(0, 4, 0), right_blue));
	world.add(make_shared<quad>(point3(-2, 3, 1), vec3(4, 0, 0), vec3(0, 0, 4), upper_orange));
	world.add(make_shared<quad>(point3(-2, -3, 5), vec3(4, 0, 0), vec3(0, 0, -4), lower_teal));

	camera cam(1, 400, 80, point3(0, 0, 9), point3(0, 0, 0));

	cam.set_samples_per_pixel(100);
	cam.set_max_depth(50);
	cam.set_defocus_angle(0);
	cam.set_background(color(0.70, 0.80, 1.00));

	cam.render(world);
}

void simple_light()
{
	hittable_list world;

	auto pertext = make_shared<noise_texture>(4);
	world.add(make_shared<sphere>(point3(0, -1000, 0), 1000, make_shared<lambertian>(pertext)));
	world.add(make_shared<sphere>(point3(0, 2, 0), 2, make_shared<lambertian>(pertext)));

	auto difflight = make_shared<diffuse_light>(color(4, 4, 4));
	world.add(make_shared<quad>(point3(3, 1, -2), vec3(2, 0, 0), vec3(0, 2, 0), difflight));
	world.add(make_shared<sphere>(point3(0, 7, 0), 2, difflight));

	camera cam(16.0 / 9.0, 400, 20, point3(26, 3, 6), point3(0, 2, 0));

	cam.set_samples_per_pixel(100);
	cam.set_max_depth(50);
	cam.set_defocus_angle(0);
	cam.set_background(color(0, 0, 0));

	cam.render(world);
}

void cornell_box()
{
	hittable_list world;

	auto red = make_shared<lambertian>(color(.65, .05, .05));
	auto white = make_shared<lambertian>(color(.73, .73, .73));
	auto green = make_shared<lambertian>(color(.12, .45, .15));
	auto light = make_shared<diffuse_light>(color(15, 15, 15));

	world.add(make_shared<quad>(point3(555, 0, 0), vec3(0, 555, 0), vec3(0, 0, 555), green));
	world.add(make_shared<quad>(point3(0, 0, 0), vec3(0, 555, 0), vec3(0, 0, 555), red));
	world.add(make_shared<quad>(point3(343, 554, 332), vec3(-130, 0, 0), vec3(0, 0, -105), light));
	world.add(make_shared<quad>(point3(0, 0, 0), vec3(555, 0, 0), vec3(0, 0, 555), white));
	world.add(make_shared<quad>(point3(555, 555, 555), vec3(-555, 0, 0), vec3(0, 0, -555), white));
	world.add(make_shared<quad>(point3(0, 0, 555), vec3(555, 0, 0), vec3(0, 555, 0), white));

	shared_ptr<hittable> box1 = box(point3(0, 0, 0), point3(165, 330, 165), white);
	box1 = make_shared<rotate_y>(box1, 15);// 先旋转后平移
	box1 = make_shared<translate>(box1, vec3(265, 0, 295));
	world.add(box1);

	// box2
	//shared_ptr<hittable> box2 = box(point3(0, 0, 0), point3(165, 165, 165), white);
	//box2 = make_shared<rotate_y>(box2, -18);// 先旋转后平移
	//box2 = make_shared<translate>(box2, vec3(130, 0, 65));
	//world.add(box2);

	// Glass Sphere
	auto glass = make_shared<dielectric>(1.5);
	world.add(make_shared<sphere>(point3(190, 90, 190), 90, glass));

	// Light Sources
	hittable_list lights;
	auto empty_material = shared_ptr<material>();
	lights.add(make_shared<quad>(point3(343, 554, 332), vec3(-130, 0, 0), vec3(0, 0, -105), empty_material));
	lights.add(make_shared<sphere>(point3(190, 90, 190), 90, empty_material));

	camera cam(1, 600, 40, point3(278, 278, -800), point3(278, 278, 0));

	cam.set_samples_per_pixel(10);
	cam.set_max_depth(50);
	cam.set_defocus_angle(0);
	cam.set_background(color(0, 0, 0));
	cam.set_lights(&lights);

	cam.render(world);
}

void cornell_smoke()
{
	hittable_list world;

	auto red = make_shared<lambertian>(color(.65, .05, .05));
	auto white = make_shared<lambertian>(color(.73, .73, .73));
	auto green = make_shared<lambertian>(color(.12, .45, .15));
	auto light = make_shared<diffuse_light>(color(7, 7, 7));

	world.add(make_shared<quad>(point3(555, 0, 0), vec3(0, 555, 0), vec3(0, 0, 555), green));
	world.add(make_shared<quad>(point3(0, 0, 0), vec3(0, 555, 0), vec3(0, 0, 555), red));
	world.add(make_shared<quad>(point3(113, 554, 127), vec3(330, 0, 0), vec3(0, 0, 305), light));
	world.add(make_shared<quad>(point3(0, 555, 0), vec3(555, 0, 0), vec3(0, 0, 555), white));
	world.add(make_shared<quad>(point3(0, 0, 0), vec3(555, 0, 0), vec3(0, 0, 555), white));
	world.add(make_shared<quad>(point3(0, 0, 555), vec3(555, 0, 0), vec3(0, 555, 0), white));

	shared_ptr<hittable> box1 = box(point3(0, 0, 0), point3(165, 330, 165), white);
	box1 = make_shared<rotate_y>(box1, 15);
	box1 = make_shared<translate>(box1, vec3(265, 0, 295));

	shared_ptr<hittable> box2 = box(point3(0, 0, 0), point3(165, 165, 165), white);
	box2 = make_shared<rotate_y>(box2, -18);
	box2 = make_shared<translate>(box2, vec3(130, 0, 65));

	world.add(make_shared<constant_medium>(box1, 0.01, color(0, 0, 0)));
	world.add(make_shared<constant_medium>(box2, 0.01, color(1, 1, 1)));

	camera cam(1, 600, 40, point3(278, 278, -800), point3(278, 278, 0));

	cam.set_samples_per_pixel(20);
	cam.set_max_depth(5);
	cam.set_defocus_angle(0);
	cam.set_background(color(0, 0, 0));

	cam.render(world);
}

void final_scene(int image_width, int samples_per_pixel, int max_depth) {
	hittable_list boxes1;
	auto ground = make_shared<lambertian>(color(0.48, 0.83, 0.53));

	int boxes_per_side = 20;
	for (int i = 0; i < boxes_per_side; i++) {
		for (int j = 0; j < boxes_per_side; j++) {
			auto w = 100.0;
			auto x0 = -1000.0 + i * w;
			auto z0 = -1000.0 + j * w;
			auto y0 = 0.0;
			auto x1 = x0 + w;
			auto y1 = random_double(1, 101);
			auto z1 = z0 + w;

			boxes1.add(box(point3(x0, y0, z0), point3(x1, y1, z1), ground));
		}
	}

	hittable_list world;

	world.add(make_shared<bvh_node>(boxes1));

	auto light = make_shared<diffuse_light>(color(7, 7, 7));
	world.add(make_shared<quad>(point3(123, 554, 147), vec3(300, 0, 0), vec3(0, 0, 265), light));

	auto center1 = point3(400, 400, 200);
	auto center2 = center1 + vec3(30, 0, 0);
	auto sphere_material = make_shared<lambertian>(color(0.7, 0.3, 0.1));
	world.add(make_shared<sphere>(center1, center2, 50, sphere_material));

	world.add(make_shared<sphere>(point3(260, 150, 45), 50, make_shared<dielectric>(1.5)));
	world.add(make_shared<sphere>(
		point3(0, 150, 145), 50, make_shared<metal>(color(0.8, 0.8, 0.9), 1.0)
	));

	auto boundary = make_shared<sphere>(point3(360, 150, 145), 70, make_shared<dielectric>(1.5));
	world.add(boundary);
	world.add(make_shared<constant_medium>(boundary, 0.2, color(0.2, 0.4, 0.9)));
	boundary = make_shared<sphere>(point3(0, 0, 0), 5000, make_shared<dielectric>(1.5));
	world.add(make_shared<constant_medium>(boundary, .0001, color(1, 1, 1)));

	auto emat = make_shared<lambertian>(make_shared<image_texture>("earthmap.jpg"));
	world.add(make_shared<sphere>(point3(400, 200, 400), 100, emat));
	auto pertext = make_shared<noise_texture>(0.2);
	world.add(make_shared<sphere>(point3(220, 280, 300), 80, make_shared<lambertian>(pertext)));

	hittable_list boxes2;
	auto white = make_shared<lambertian>(color(.73, .73, .73));
	int ns = 1000;
	for (int j = 0; j < ns; j++) {
		boxes2.add(make_shared<sphere>(point3::random(0, 165), 10, white));
	}

	world.add(make_shared<translate>(
		make_shared<rotate_y>(
			make_shared<bvh_node>(boxes2), 15),
		vec3(-100, 270, 395)
	)
	);

	camera cam(1, image_width, 40, point3(478, 278, -600), point3(278, 278, 0));

	cam.set_samples_per_pixel(samples_per_pixel);
	cam.set_max_depth(max_depth);
	cam.set_defocus_angle(0);
	cam.set_background(color(0, 0, 0));

	cam.render(world);
}